package meme;
import javax.swing.*;

import util.bio.parsers.*;
import util.gen.*;

import java.util.*;

/**
 * Main, wRapper for meme geared toward processing multiple rounds of selex data.
 * 
 * This is a major revision of MemeR, I've equipped it for mass data processing.  Multiple files can be submitted.  Each
 * is searched for the presence of motifs using UCSD's meme program.  These motifs are stored and then used to search the
 * the sequences in each file for the presence of the motif, hits above a user defined cut off.  Multiple results and
 * summary tables are printed and saved. The program works by first firing the main method in MemeR which in turn creates
 * a MemeResults object that will hold most of the data generated by the program.  User inputs are processed asking for:
 * 1)parsed selex multi-FASTA files separated by a comma 2)meme command line arguments 3)a meme cutoff score 4)a
 * directory to save the result files 5) a basename to use in naming the result files.  UCSD's meme is then fired for each
 * file entered.  A MemeParser is created to parse the meme output, a MemeMotif is created for each motif parsed from the
 * meme output.  Once the motifs have been created.  A MotifScanner object is created from a motif and a fasta file and
 * executes a search for motif hits.  A MotifSearchResult object is used to hold the results from this search.  If there
 * were any hits, these are stored as an array of MotifHit objects in the MotifSearchResult object.  So here's the flow
 * MemeR->MemeParser->MemeMotif(s)      MemeMotif->MotifScanner->MotifSearchResult->MotifHit(s)
 * Lastly, several MemeResults methods are called from MemeR to print various reports/ summaries to the screen and also
 * write them to file.  These include a summary of the motifs identified, a summary table of motif hits in each file,
 * and a crude printed graph showing the percentage of sequences in each multi-fasta file that contained each motif.
 * 
 * nix@uclink.berkeley.edu 22 Oct 2004 MemeR(0.2.1)
 */
public class MemeR {
    
    //fields
    private String fullPathToSapoMeme = "/home/sapo/software/seqanal/motifs/meme/meme.3.0.4/bin/meme"; //for parallel meme on Sapo
    private boolean runParallelMemeR = false; //set to false to run single processor MemeR
    
    private String memeCommand; //command line args used to fire MEME
    private String[] dataFiles; //multi FASTA files containing the seqs
    private double cutOffScore; //cut off score used to limit which seqs are scored as a real hit
    private String resultsDir;  //directory where the results are to be written
    private String fileBaseName; //base name to use when writting result files
    private MemeParser[] mps;  //array of MemeParsers, one for each seq FASTA file submitted
    private MemeResults results;
    private ArrayList motifResults; //array of MotifSearchResult objects generated by taking each motif generated
    //from each file and searching all the sequences in all the files, use these to get all the info desired
    
    //constructor
    public MemeR(){
        results = new MemeResults();    //create an object to hold all the results needed to generate reports
        results.printSave(UtilMeme.getDocs());  //the printSave method is used throughout to print to the screen and also same lines to a StringBuffer
        
        getUserInputs();  //get user inputs and checks files
        results.printSave("\nLaunching MEME...\nMEME takes minutes to hours to run depending on the number of input sequences.\n    ...and, of course, the burliness of your box...\n");
        
        fireMeme();  //launch the C meme program once for each fasta file
        
        searchDataFiles();  //search each fasta file with all the motifs identified by fireMeme()
        
        results.setMotifResults(motifResults); //put an array of MotifSearchResult objects into the MemeResults object
        results.setMotifSummaryText(results.getMotifSummary()); //printSave a summary of the motifs identified by meme
        results.setMotifHitsSummaryText(results.getMotifHitsSummary());  //printSave a summary of the motif hits with full stats!
        results.setMotifHitPlotText(results.getMotifHitPlot());  //printSave a crude plot of the motif hits percent
        
        //write results
        String screenDump = UtilMeme.makeFullPathName(resultsDir, fileBaseName+".screenDump");
        String motifs = UtilMeme.makeFullPathName(resultsDir, fileBaseName+".motifs");
        String sumResults = UtilMeme.makeFullPathName(resultsDir, fileBaseName+".sumResults");
        IO.writeString(results.getResults(), screenDump);
        IO.writeString(results.getMemeReports(), motifs);
        IO.writeString(results.getMotifSummaryText()+ results.getMotifHitsSummaryText() + results.getMotifHitPlotText(),sumResults);
        
        System.out.println("Results file saved -> " + screenDump+"\n");
        System.out.println("Results file saved -> " + motifs+"\n");
        System.out.println("Results file saved -> " + sumResults+"\n\nDone\n");
        System.exit(0);
    }
    
    public static void main(String[] args)  {
        //check to see if they want to run...
        if (args.length == 0 || args[0].equals("-h") || args[0].equals("help")){
            UtilMeme.printDocs();
            System.exit(0);
        }
        MemeR mr = new MemeR();
    }
    
    //primary methods
    public void fireMeme() {
        mps = new MemeParser [dataFiles.length];
        //make array of MemeParsers
        for (int i=0; i<dataFiles.length; i++){
            results.appendMemeParserReport("\nAttempting to create motifs from seqs in "+dataFiles[i]+"\n");
                        
            //for parallel processor meme tweaked for Sapo
            if (runParallelMemeR) mps[i] = new MemeParser(fullPathToSapoMeme, dataFiles[i], memeCommand);
			
			//for single processor meme
            else {	
				String command = "meme "+ dataFiles[i] + " " + memeCommand;
				mps[i] = new MemeParser(command);
            }
            
            if (mps[i].getNumMotifs() == 0) results.appendMemeParserReport("No motifs were found by MEME? Check to see if it works on the command line.\n\n");
            else results.appendMemeParserReport(mps[i].getNumMotifs()+ " motifs were found.\n");
        }
        //print parser reports
        results.appendMemeReports("Printing Meme Reports for each file\n");
        for (int i=0; i<mps.length; i++){
            results.appendMemeReports("\n************************ Meme Report ***************************\n"+
            mps[i]+
            "\n****************************************************************\n");
        }
        //Set a ref to the Array of MemeParsers in the MemeResults object
        results.setMemeParserArrayRef(mps);
    }
    
    public void searchDataFiles() {
        //for each motif parser that contains motifs, get it's motifs and scan for hits in all files
        motifResults = new ArrayList();  //array of MotifSearchResult objects
        for (int i=0; i<mps.length; i++) {
            //check to see if any motifs were found
            if (mps[i].getNumMotifs()==0) continue;
            
            //get motifs and the sequences to scan for each motif
            MemeMotif[] memeMotifs = mps[i].getMemeMotifs();
            
            //for all files, scan for the presence of each motif
            for (int k=0; k< dataFiles.length; k++) {
                MultiFastaParser mfp = new MultiFastaParser(dataFiles[k]);
                String[] seqs = mfp.getSeqs();
                String[] names = mfp.getNames();
                
                //for each motif make a motif scanner and scan the test file, lastly use a MotifSearchResult obj to
                //  hold the results
                for (int j=0; j<memeMotifs.length; j++){
                    //make an obj to hold the results, initialize with a ref to the motif which has a ref to the MemeParser
                    MotifSearchResult msResult = new MotifSearchResult(memeMotifs[j], dataFiles[k], seqs.length);
                    
                    results.appendMotifSearchResults("\nScanning "+dataFiles[k]+" with Motif: "+(j+1)+"\n   "+memeMotifs[j].getMotifSumLn()+
                    "\n   "+"Generated with :"+ mps[i].getCmdLn()+"\n\n");
                    
                    //make two motifScanners, one for each LLPSPM's
                    
                    //meme motif
                    results.appendMotifSearchResults("\nUsing MEME's Log Likelihood PSPM...\n");
                    MotifScanner motifScannerMemePSPM = new MotifScanner(memeMotifs[j].getLLMemePSPM(0.25, 0.25, 0.25, 0.25), results);
                    double cutOffMeme;
                    if (cutOffScore== 12345.0){ //default
                        cutOffMeme = motifScannerMemePSPM.findLowestScoringSeq(memeMotifs[j].getHits());
                    }
                    else cutOffMeme = cutOffScore;
                    results.appendMotifSearchResults("Cut off score: "+cutOffMeme+"\n");
                    msResult.setMemeLLPSPMCutOffScore(cutOffMeme);
                    int numSeqsMeme = motifScannerMemePSPM.scanEm(seqs, names, cutOffMeme);
                    msResult.setNumHitsMemeLLPSPM(numSeqsMeme);
                    msResult.setHitsMemeLLPSPM(motifScannerMemePSPM.getMotifHits());
                    
                    //add one motif
                    results.appendMotifSearchResults("\nUsing an Add One Psuedo Count Log Likelihood PSPM...\n");
                    MotifScanner motifScannerAddOnePSPM = new MotifScanner(memeMotifs[j].getLLAdd1PSPM(0.25, 0.25, 0.25, 0.25), results);
                    double cutOffAddOne;
                    if (cutOffScore == 12345.0){
                        cutOffAddOne = motifScannerAddOnePSPM.findLowestScoringSeq(memeMotifs[j].getHits());
                    }
                    else cutOffAddOne = cutOffScore;
                    results.appendMotifSearchResults("Cut off score: "+cutOffAddOne+"\n");
                    msResult.setAddOneLLPSPMCutOffScore(cutOffAddOne);
                    int numSeqsAddOne = motifScannerAddOnePSPM.scanEm(seqs, names, cutOffAddOne);
                    msResult.setNumHitsAddOneLLPSPM(numSeqsAddOne);
                    msResult.setHitsAddOneLLPSPM(motifScannerAddOnePSPM.getMotifHits());
                    
                    //summary and save numbers
                    results.appendMotifSearchResults("\nSequences containing Motif using MEME's LL PSPM: "+numSeqsMeme+
                    "\n\nSequences containing Motif using the Add One LL PSPM: "+numSeqsAddOne+"\n");
                    motifResults.add(msResult);
                }
            }
        }
    }
    /** Uses JOptionPane dialog input boxes to get user inputs*/
    public void getUserInputs(){
        String title;
        String comment;
        String suggestion;
        results.printSave("Waiting for user inputs...\n\n");
        
        //get files to process
        comment = "Please enter the names of parsed SELEX multi-FASTA files.  Separate multiple files with a comma.  You many need to use a full path description.\n   (Hint: Pay attention to the order of your files.  It is used to organize the result tables and graph.";
        title = "Parsed Selex Multi-FASTA Files";
        suggestion = "/Users/nix/selex/giantRnd3, /Users/nix/selex/giantRnd2, /Users/nix/selex/giantRnd1";
        boolean flag = true;
        while (flag) {
            String fileStr = makeInputDialog(title, comment, suggestion);
            fileStr = fileStr.replaceAll("\\s", "");  //kill spaces
            dataFiles = fileStr.split(",");
            for (int i=0; i<dataFiles.length; i++){
                if (UtilMeme.checkFile(dataFiles[i])==false){
                    JOptionPane.showMessageDialog(null, "Cannot find your file?! Did you use the full path? Press OK to try again.\n   -> "+dataFiles[i], "File Not Found", JOptionPane.ERROR_MESSAGE);
                    suggestion = fileStr;
                    flag = true;
                    break;
                }
                else flag = false;
            }
        }
        
        results.appendSelexOligoFiles("Parsed Selex Multi-FASTA Files:\n");
        for (int i=0; i<dataFiles.length; i++){
            results.appendSelexOligoFiles("\t"+dataFiles[i]+"\n");
        }
        
        //get meme command line field
        comment = "Please enter MEME command line arguments. Try it first with a test sequence in a terminal to see if it runs correctly. See the MEME \n     instructions for option details. The following would be a good start for SELEX data.  Be sure that the meme program is in your path.\n     Typing 'meme' on the command line should bring up its usage statements.";
        title = "MEME Command Line Args";
        suggestion = "-nmotifs 10 -evt 0.001 -minw 4 -maxw 10 -mod zoops";
        
        memeCommand = makeInputDialog(title, comment, suggestion) + " -dna -revcomp -text -nostatus";
        results.setMemeCmdLnArgs(memeCommand);
        results.printSave("\nMeme Command Line Args: "+memeCommand+"\n\n");
        
        //get score cut off
        comment =
        "Please enter a matrix match cut off score (a log-likelihood PSPM value).  Sequences\n"+
        "   falling below this number will not be saved. Enter \"use default\" if you\n"+
        "   want the lowest score from the sub sequences used in creating the PSPMs.";
        title = "Matrix Match Cut Off Score";
        suggestion = "use default";
        String cutOffString = makeInputDialog(title, comment, suggestion);
        if (cutOffString.equals("use default")) {
            cutOffScore = 12345;
            results.printSave("Matrix Cut Off Score: default\n\n");
        }
        else {
            cutOffScore = Double.parseDouble(cutOffString);
            results.printSave("Matrix Cut Off Score: "+cutOffScore +"\n");
        }
        
        //get where the user wants the results written
        comment = "Where would you like to save the results?  Use a full path.\n";
        title = "Results Directory";
        suggestion = "/my/results/directory";
        
        flag = true;
        while (flag) {
            resultsDir = makeInputDialog(title, comment, suggestion);
            if (UtilMeme.checkFile(resultsDir) == false){
                JOptionPane.showMessageDialog(null, "Cannot find your results directory?! Did you use the full path? Press OK to try again.\n   -> "+resultsDir, "Directory Not Found", JOptionPane.ERROR_MESSAGE);
                suggestion = resultsDir;
            }
            else flag = false;
        }
        resultsDir = resultsDir.replaceAll("/$","");
        results.setResultsDirectory("Results Directory: "+resultsDir +"\n");
        
        //get the base name the user wants to save files
        comment = "What base name would you like to use in naming the result files?";
        title = "Base File Name";
        suggestion = "GiantSelexExp7";
        
        flag=true;
        while (flag) {
            fileBaseName = makeInputDialog(title, comment, suggestion);
            fileBaseName = fileBaseName.trim();
            if (fileBaseName.equals("")==false) flag=false;
        }
        results.setFileBaseName("File Base Name: "+fileBaseName +"\n");
    }
    public static String makeInputDialog(String title, String comment, String suggestion){
        /**make a dialog input box*/
        Object ob = JOptionPane.showInputDialog(null,comment, title , JOptionPane.PLAIN_MESSAGE, null, null, suggestion);
        if (ob == null){// if user hits cancel or closes the window
            System.out.println("\n\n Good bye...  No files were written.\n\n");
            System.exit(0);
        }
        String userInput = String.valueOf(ob);
        return userInput.trim();
    }   
}
